// 错误处理
package main

import (
	"errors"
	"fmt"
)

// 按照惯例，错误通常是最后一个返回值并且是 error 类型，一个内建的接口。
func f1(arg int) (int, error) {
	if arg == 42 {
		// errors.New 构造一个使用给定的错误信息的基本 error 值。
		return -1, errors.New("can't work with 42")
	}

	// 返回错误值为 nil 代表空值，没有错误。
	return arg + 3, nil
}

// 通过实现 Error 方法来自定义 error 类型是可以的。这里使用自定义错误类型来表示上面的参数错误。
type argError struct {
	arg  int
	prob string
}

// 自定义错误类型必要的定义的方法（传入的是指针，所以后面定义的结构体出来的也是指针）
func (e *argError) Error() string {
	return fmt.Sprintf("%d - %s", e.arg, e.prob)
}

func f2(arg int) (int, error) {
	if arg == 42 {
		// 在这个例子中，我们使用 &argError 语法来建立一个新的结构体，并提供了 arg 和 prob 这个两个字段的值。
		// 因为第二个返回值的类型在该函数为 error，因此该返回值必须要有一个 Error() 方法，正如我们前面定义过的，到时候会自动调用该方法表示错误
		return -1, &argError{arg, "can't work with it"}
	}
	return arg + 3, nil
}

func main() {

	// 下面的两个循环测试了各个返回错误的函数。注意在 if 行内的错误检查代码，在 Go 中是一个普遍的用法。
	for _, i := range []int{7, 42} {
		// 先赋值，再判断错误是否为空，不为空就 failed，为空就 worked
		if r, e := f1(i); e != nil {
			fmt.Println("f1 failed:", e)
		} else {
			fmt.Println("f1 worked:", r)
		}
	}

	for _, i := range []int{7, 42} {
		if r, e := f2(i); e != nil {
			fmt.Println("f2 failed:", e)
		} else {
			fmt.Println("f2 worked:", r)
		}
	}

	// 你如果想在程序中使用一个自定义错误类型中的数据，你需要通过类型断言来得到这个错误类型的实例。
	// 这里 42 会返回自定义错误类型，注意是指针类型。
	// e.(T) 的写法为断言，括号内断言 e 是啥类型的，我们前面定义该类型的 Error() 方法的时候用的是 *argError，所以这里就这样写
	// 断言成功，ae 返回 e 的 Error() 方法返回的内容，ok 为 true；断言失败，ae，ok 均为 nil。后半句 ok 判断为 true 才会执行 if 内的代码
	_, e := f2(42)
	if ae, ok := e.(*argError); ok {
		fmt.Println(ae.arg)
		fmt.Println(ae.prob)
	}
}
